﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Amazon.Glacier;
using Amazon.Glacier.Model;
using Amazon.Glacier.Transfer;

namespace AmazonGlacierGUI.Core.Helpers.AmazonGlacier
{
    //22-Aug-2012 Priyan R
    public class AmazonGlacierUploadHelper : AmazonGlacierHelperBase
    {
        public event EventHandler<ProgressEventArgs> OnProgress;
        public event EventHandler<ErrorEventArgs> OnError;
        public event EventHandler<CompletedEventArgs> OnComplete;
        public event EventHandler<MultiPartInitiatedEventArgs> OnMultiPartInitiated;
        public event EventHandler OnCancancelled;

        private const long MultiPartMinSizeRequired = 1024*1024;
        private const long MultiPartSize =  (1024*1024)*1;
        private CancellationTokenSource _cancelTokenSource;
        List<string> _partChecksumList = new List<string>();
        public void Test()
        {
                                    
        }
        public void Upload(string filepath,string description)
        {
            _cancelTokenSource = new CancellationTokenSource();
            Task.Factory.StartNew(() =>
            {
                
                try
                {
                    FileSize = new FileInfo(filepath).Length;
                    IsProcessing = true;
                    if (FileSize < MultiPartMinSizeRequired)
                    {
                        UploadNormal(filepath, description);
                    }
                    else
                    {
                        IsMultipPartUpload = true;
                        UploadMultiPart(filepath, description); 
                    }
                }
                catch (Exception ex)
                {
                    IsProcessing = false;
                    RaiseError(GetExceptionMessage(ex));                    
                }

            }, _cancelTokenSource.Token);
        }

        private void UploadNormal(string filepath, string description)
        {            
            try
            {
                InitClient();
                var tm = new ArchiveTransferManager(_client);
                var id = tm.Upload(VaultName, description, filepath).ArchiveId;
                IsProcessing = false;
                RaiseComplete(id);
            }
            catch (Exception ex)
            {
                IsProcessing = false;
                RaiseError(GetExceptionMessage(ex));
            }

        }
        private void UploadMultiPart(string filepath, string description)
        {
            _cancelTokenSource = new CancellationTokenSource();
            try
            {
                InitClient();
                var mpuInitiateRequest = new InitiateMultipartUploadRequest()
                {

                    VaultName = VaultName,
                    PartSize = MultiPartSize,
                    ArchiveDescription = description
                };
                var mupuInitiateResponse = _client.InitiateMultipartUpload(mpuInitiateRequest);
                RaiseMultiPartInitiated(mupuInitiateResponse.InitiateMultipartUploadResult.UploadId);
                long position = 0;                
                while (position < FileSize)
                {
                    if (_cancelTokenSource.IsCancellationRequested)
                    {
                        RaiseCancelled();
                        return;
                    }
                    string checksum = UploadPart(mupuInitiateResponse.InitiateMultipartUploadResult.UploadId, filepath, position);
                    position = position + Math.Min(MultiPartSize, FileSize - position);
                    RaiseProgress(position, checksum);
                }
                var id = CompleteMultiPartUpload(mupuInitiateResponse.InitiateMultipartUploadResult.UploadId, filepath,
                                                 _partChecksumList);
                RaiseComplete(id);
            }
            catch (Exception ex)
            {
                IsProcessing = false;
                RaiseError(GetExceptionMessage(ex));
            }
            finally
            {
                IsProcessing = false;
            }

        }
        public void ResumeUpload(string filepath,long uploadedBytes,string uploadId)
        {
            _cancelTokenSource = new CancellationTokenSource();
            try
            {
                IsProcessing = true;
                InitClient();
                FileSize = new FileInfo(filepath).Length;
                _partChecksumList = new List<string>();
                IsProcessing = true;
                long position = 0;
                using (FileStream fs = new FileStream(filepath, FileMode.Open, FileAccess.Read))
                {
                    while (position < uploadedBytes)
                    {
                        fs.Seek(position, SeekOrigin.Begin);
                        Stream uploadPartStream = GlacierUtils.CreatePartStream(fs, MultiPartSize);
                        _partChecksumList.Add(TreeHashGenerator.CalculateTreeHash(uploadPartStream));
                        position = position + MultiPartSize;
                    }
                }                
                Task.Factory.StartNew(() =>
                                          {
                                              ResumeUploadAsync(filepath, uploadedBytes, uploadId);
                                          });
            }
            catch (Exception ex)
            {
                IsProcessing = false;
                RaiseError(GetExceptionMessage(ex));
            }
          

        }
        private void ResumeUploadAsync(string filepath, long uploadedBytes, string uploadId)
        {
            try
            {

                long position = uploadedBytes;                                            
                while (position < FileSize)
                {
                    if (_cancelTokenSource.IsCancellationRequested)
                    {
                        RaiseCancelled();
                        return;
                    }
                    string checksum = UploadPart(uploadId, filepath, position);
                    position = position + Math.Min(MultiPartSize, FileSize - position);
                    RaiseProgress(position, checksum);
                }
                var id = CompleteMultiPartUpload(uploadId, filepath,_partChecksumList);
                RaiseComplete(id);
            }
            catch (Exception ex)
            {
                IsProcessing = false;
                RaiseError(GetExceptionMessage(ex));
            }
            finally
            {
                IsProcessing = false;
            }

        }  
        string UploadPart(string uploadId,string filePath,long position)
        {
            using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            {
                fs.Seek(position, SeekOrigin.Begin);
                Stream uploadPartStream = GlacierUtils.CreatePartStream(fs, MultiPartSize);
                string checksum = TreeHashGenerator.CalculateTreeHash(uploadPartStream);          
                _partChecksumList.Add(checksum);                
                var uploadMPUrequest = new UploadMultipartPartRequest()
                                                                  {

                                                                      VaultName = VaultName,
                                                                      Body = uploadPartStream,
                                                                      Checksum = checksum,
                                                                      UploadId = uploadId
                                                                  };
                uploadMPUrequest.SetRange(position, position + uploadPartStream.Length-1);
                _client.UploadMultipartPart(uploadMPUrequest);
                return checksum;
            }
        }
        
        string CompleteMultiPartUpload(string uploadId,string filePath,List<string> partChecksumList)
        {            
            var completeMPUrequest = new CompleteMultipartUploadRequest()
            {
                UploadId = uploadId,
                ArchiveSize = FileSize.ToString(),
                Checksum = TreeHashGenerator.CalculateTreeHash(partChecksumList),
                VaultName = VaultName
            };
            CompleteMultipartUploadResponse completeMPUresponse = _client.CompleteMultipartUpload(completeMPUrequest);
            return completeMPUresponse.CompleteMultipartUploadResult.ArchiveId;
        }
        public void CancelMultiPartUpload()
        {
            if (!IsProcessing) return;            
            _cancelTokenSource.Cancel();

        }
      

        void RaiseProgress(long bytesUploaded,string partChecksum)
        {
            if(OnProgress==null) return;
            OnProgress(this, new ProgressEventArgs(bytesUploaded,partChecksum));
        }
        void RaiseComplete(string archiveId)
        {
            if (OnComplete == null) return;
            OnComplete(this, new CompletedEventArgs(archiveId));
        }
        void RaiseError(string errorMessage)
        {
            if (OnError == null) return;
            OnError(this, new ErrorEventArgs(errorMessage));
        }
        void RaiseMultiPartInitiated(string uploadId)
        {
            if(OnMultiPartInitiated==null) return;
            OnMultiPartInitiated(this, new MultiPartInitiatedEventArgs(uploadId));
        }

        void RaiseCancelled()
        {
            if (OnCancancelled == null) return;
            OnCancancelled(this,null);
        }
        public long FileSize { get; private set; }
     
        public bool IsMultipPartUpload { get; private set; }     
        public class ProgressEventArgs:EventArgs
        {
            

            public ProgressEventArgs(long uploadedBytes, string partCheckSum)
            {
                UploadedBytes = uploadedBytes;
                PartCheckSum = partCheckSum;
            }

            public long UploadedBytes { get; set; }            
            public string PartCheckSum { get; set; }

        }
        public class ErrorEventArgs : EventArgs
        {
            public ErrorEventArgs(string errorMessage)
            {
                ErrorMessage = errorMessage;
            }

            public string ErrorMessage { get; set; }
        }
        public class CompletedEventArgs : EventArgs
        {
            public CompletedEventArgs(string archiveId)
            {
                ArchiveId = archiveId;
            }
            public string ArchiveId { get; set; }
        }
        public class MultiPartInitiatedEventArgs : EventArgs
        {
            public MultiPartInitiatedEventArgs(string uploadId)
            {
                UploadId = uploadId;
            }

            public string UploadId { get; set; }
        }
      
    }
}
